org 0x900

jmp CODE16_START

%include "inc.asm"
%include "common.asm"


; 全局描述符表定义
;                                 段基址         段界限                   段属性
GDT_BASE         : Descriptor     0,             0,                      0
CODE32_DESC      : Descriptor     0,             CODE32_SEG_LEN - 1,     DA_C + DA_32 + DA_DPL0
VIDEO_DESC       : Descriptor     0xB8000,       0xBFFFF - 0xB8000,      DA_DRWA + DA_32 + DA_DPL3
CODE32_FLAT_DESC : Descriptor     0,             0xFFFFF,                DA_C + DA_32 + DA_LIMIT_4K + DA_DPL0
DATA32_FLAT_DESC : Descriptor     0,             0xFFFFF,                DA_DRW + DA_32 + DA_LIMIT_4K + DA_DPL0
TSS_DESC         : Descriptor     0,             TSS_LEN -1,             DA_TSS + DA_DPL0 
LDT_DESC         : Descriptor     0,             LDT_LEN - 1,            DA_LDT + DA_DPL0
PAGE_DIR_DESC    : Descriptor     PAGE_DIR_BASE, 4095,                   DA_DRW + DA_32
PAGE_TAB_DESC    : Descriptor     PAGE_TAB_BASE, 1023,                   DA_DRW + DA_32 + DA_LIMIT_4K
; ...

GDT_LEN         equ   $ - GDT_BASE  ; GDT 长度 = 当前地址 - GDT_BASE 地址

GDT_PTR :
    dw   GDT_LEN - 1
    dd   GDT_BASE

; 段选择符定义
CODE32_SELECTOR      equ   (0x0001 << 3) + SA_RPL0 + SA_TIG
VIDEO_SELECTOR       equ   (0x0002 << 3) + SA_RPL3 + SA_TIG
CODE32_FLAT_SELECTOR equ   (0x0003 << 3) + SA_RPL0 + SA_TIG
DATA32_FLAT_SELECTOR equ   (0x0004 << 3) + SA_RPL0 + SA_TIG
TSS_SELECTOR         equ   (0x0005 << 3) + SA_RPL0 + SA_TIG
LDT_SELECTOR         equ   (0x0006 << 3) + SA_RPL0 + SA_TIG
PAGE_DIR_SELECTOR    equ   (0x0007 << 3) + SA_RPL0 + SA_TIG
PAGE_TAB_SELECTOR    equ   (0x0008 << 3) + SA_RPL0 + SA_TIG


msg db "Loader..."	; 使用 db 定义数据
msgLen equ $-msg	; 字符串 msg 长度

TSS_SEGMENT:
    dd    0
    dd    BOOT_START_ADDR     ; esp0
    dd    DATA32_FLAT_SELECTOR; ss0
    dd    0                   ; esp1
    dd    0                   ; ss1
    dd    0                   ; esp2
    dd    0                   ; ss2
    times 4 * 18 dd 0
    dw    0
    dw    $ - TSS_SEGMENT + 2
    db    0xFF   
TSS_LEN    equ    $ - TSS_SEGMENT


; 局部段描述符表定义
;                                 段基址        段界限                   段属性
LDT_BASE         :
LDT_VIDEO        : Descriptor     0xB8000,      0xBFFFF - 0xB8000,      DA_DRWA + DA_32 + DA_DPL3
LDT_CODE_DESC    : Descriptor     0,            0xFFFFF,                DA_C + DA_32 + DA_DPL3
LDT_DATA_DESC    : Descriptor     0,            0xFFFFF,                DA_DRW + DA_32 + DA_DPL3
; ...
LDT_LEN      equ   $ - LDT_BASE  ; 长度 = 当前地址 - LDT_BASE 地址

; 局部段选择符定义，RPL = 3; TI = 1
LDT_VIDEO_SELECTOR   equ   (0x0000 << 3) + SA_RPL3 + SA_TIL
LDT_CODE_SELECTOR    equ   (0x0001 << 3) + SA_RPL3 + SA_TIL
LDT_DATA_SELECTOR    equ   (0x0002 << 3) + SA_RPL3 + SA_TIL


; IDT 中断描述符表定义
;          选择子，               偏移地址，  参数个数，  属性 
IDT_BASE: 
    %rep 256
    Gate   CODE32_FLAT_SELECTOR,  0,         0,         DA_INTR_GATE + DA_DPL3
    %endrep
IDT_LEN     equ     $ - IDT_BASE 

IDT_PTR :
    dw   IDT_LEN - 1
    dd   IDT_BASE


[bits 16]
CODE16_START :
    xor ax, ax		; xor 指令与 and or 指令类似，两个操作数的每一对对应位都应用如下操作原则：
					; 如果两个位的值相同（同为 0 或同为 1），则结果位等于 0；否则结果位等于 1
					; xor ax, ax 等于 ax = 0
	mov ss, ax		; 栈段 ss = 0
	mov ds, ax		; 数据段 ds = 0
	mov es, ax		; 附加数据段 es = 0 
	mov sp, BOOT_START_ADDR	; 设置栈

    ; 设置光标位置 (dl, dh)
	mov ah, 0x02    ; AH 功能号 = 0x02，设置光标
	mov dh, 0x01    ; 设置光标行号
	mov dl, 0x00    ; 设置光标列号
	mov bh, 0x00    ; 页码
	int 0x10

    ; 打印 msg
	mov ax, msg
	mov cx, msgLen
	call print

    ; IOPL = 3
    pushf
    pop eax 
    or eax, 0x3000 ; bit12-bit13:11b
    push eax
    popf

    ; 将硬盘扇区 1 中的数据读入到内存  0x700 处
	mov eax, 0x01
	mov bx, 0x700
    mov cx, 1
	call rd_disk_to_mem

	; 将硬盘扇中 kernel 数据读入到内存  0xB000 处
    mov eax, 0
	mov ax, [0x700]    ; 0x700 处存的是 loader.bin 所占的扇区数，再 +2 找打 kernel 起始扇区 
    add ax, 2
    mov cx, [0x702]
    mov dx, 0xb00
    mov ds, dx
    mov bx, 0x0
	call rd_disk_to_mem
    ; 需恢复 ds=0, 下面的程序需要 ds 为 0
    mov dx, 0x0
    mov ds, dx

    ; 将硬盘扇中 app 数据读入到内存  0x80000 处 
    mov ax, [0x700]     ; loader 所占扇区数
    add ax, [0x702]     ; + kernel 扇区数
    add ax, 2           ; + 2 得到 app.bin 起始扇区 
    mov cx, [0x704]
    ; 因为 app 加载地址 0x80000 超过 0xFFFF，通过改动段基址 ds = 0x8000 实现访问内存地址 0x80000
    mov dx, 0x8000
    mov ds, dx
    mov bx, 0x0000
    call rd_disk_to_mem
    ; 需恢复 ds=0, 下面的程序需要 ds 为 0
    mov dx, 0x0
    mov ds, dx

    ; 初始化段描述符中的段基址
    mov esi, CODE32_START
    mov edi, CODE32_DESC
    call InitDescItem

    mov esi, TSS_SEGMENT
    mov edi, TSS_DESC
    call InitDescItem
    
    mov esi, LDT_BASE
    mov edi, LDT_DESC 
    call InitDescItem

    ; 将需要的数据放入共享内存中
    call PutDataToShare
    
    ; 打开 A20 地址线
    in al, 0x92 
    or al, 0000_0010B 
    out 0x92, al

    ; 加载描述符表
    ;mov eax, 0
    ;mov ax, ds
    ;shl eax, 4
    ;add eax, GDT_BASE
    ;mov dword [GDT_PTR + 2], eax
    lgdt [GDT_PTR]

    ; 加载中断描述表
    lidt [IDT_PTR]

    ; 通知 CPU 进入保护模式，即将 CR0 寄存器的 PE（bit0） 位置 1
    mov eax, cr0
    or eax, 0x01 
    mov cr0, eax

    ; 刷新流水线
    ; 跳转到 32 位代码段继续执行
    jmp dword CODE32_SELECTOR:0


; 参数： esi --> 代码段标签
; 参数： edi --> 段描述符标签
InitDescItem:
    push eax

    mov eax, 0
    mov ax, cs
    shl eax, 4
    add eax, esi
    mov word [edi + 2], ax
    shr eax, 16
    mov byte [edi + 4], al
    mov byte [edi + 7], ah

    pop eax
    ret

; 将数据放入共享内存
PutDataToShare:
    ; 将 GDT 描述符表基地址放到共享内存 GDT_ENTRY_ADDR 中
    mov dword [GDT_ENTRY_ADDR], GDT_BASE
    ; 将 GDT 描述符个数放到共享内存 GDT_SIZE_ADDR 中
    mov dword [GDT_SIZE_ADDR], GDT_LEN / 8
    
    ; 将 LDT 描述符表基地址放到共享内存 LDT_ENTRY_ADDR 中
    mov dword [LDT_ENTRY_ADDR], LDT_BASE
    ; 将 GDT 描述符个数放到共享内存 LDT_SIZE_ADDR 中
    mov dword [LDT_SIZE_ADDR], LDT_LEN / 8

    ; 将 IDT 描述符表基地址放到共享内存 IDT_ENTRY_ADDR 中
    mov dword [IDT_ENTRY_ADDR], IDT_BASE
    ; 将 IDT 描述符个数放到共享内存 IDT_SIZE_ADDR 中
    mov dword [IDT_SIZE_ADDR], IDT_LEN / 8

    ; 将 TSS 基地址放到共享内存 TSS_ENTRY_ADDR 中
    mov dword [TSS_ENTRY_ADDR], TSS_SEGMENT
    ; 将 TSS 大小放到共享内存 TSS_SIZE_ADDR 中
    mov dword [TSS_SIZE_ADDR], TSS_LEN
 
    ret


[bits 32]
CODE32_START:
    ; 设置显存段
	mov ax, VIDEO_SELECTOR
    mov gs, ax 

    mov ax, DATA32_FLAT_SELECTOR
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov ss, ax
    mov esp, BOOT_START_ADDR

    ; 加载任务状态段 TSS
    mov ax, TSS_SELECTOR
    ltr ax

    ; 加载局部段描述符表
    mov ax, LDT_SELECTOR
    lldt ax

    ; 创建页目录页表
    call setup_page

    jmp dword CODE32_FLAT_SELECTOR:KERNEL_START_ADDR
    jmp $


; 创建页目录及页表
setup_page:
    push eax
    push ecx
    push edi
    push es

    ; 创建页目录
    mov ax, PAGE_DIR_SELECTOR
    mov es, ax
    mov edi, 0          ; es:edi -> PAGE_DIR_SELECTOR:0
    mov ecx, 1024       ; 循环 1024 次，页目录共 1024 项
    mov eax, PAGE_TAB_BASE + PG_P + PG_US_U + PG_RW_W ; eax = 第一个子页表的地址和属性
    cld                 ; edi 自增
.creat_pde:             ; 创建页目录项
    stosd               ; 把 eax 中的值写入 [es:edi] 指向的内存中
    add eax, 4096       ; eax = eax + 4K
    loop .creat_pde
    
    ; 程序执行到这里说明页目录表已经创建完成
    
    ; 创建页表
    mov ax, PAGE_TAB_SELECTOR
    mov es, ax
    mov edi, 0          ; es:edi -> PAGE_TAB_SELECTOR:0
    mov ecx, 1024*1024  ;  循环 1024*1024 次，共 1024 个页表，每个页表有 1024 个页表项
    mov eax, PG_P + PG_US_U + PG_RW_W ; 只有属性，没有基地址，因为第一个页表项指向的就是物理地址 0x0 处
    cld
.creat_pte:             ; 创建页表项
    stosd               ; 把 eax 中的值写入 [es:edi] 指向的内存中
    add eax, 4096       ; eax = eax + 4K
    loop .creat_pte

    ; 将页目录表首地址写入控制寄存器 cr3
    mov eax, PAGE_DIR_BASE
    mov cr3, eax

    ; 寄存器 cr0 的 PG 位置 1
    ; mov eax, cr0
    ; or  eax, 0x80000000
    ; mov cr0, eax

    pop es
    pop edi
    pop ecx
    pop eax

    ret


CODE32_SEG_LEN equ $-CODE32_START



